﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;

namespace ES.Migrate
{
    public class MigrateWorker<T>
    {
        /// <summary>
        /// 数据包大小(默认16mb)
        /// </summary>
        public static long PackSize { get; set; } = 1024 * 1024 * 16;

        public event EventHandler<OnStartEventArgs<T>> OnStart;

        public event EventHandler<OnUpdateProgressEventArgs<T>> OnUpdateProgress;

        public event EventHandler<OnErrorEventArgs<T>> OnError;

        public event EventHandler<OnMd5CheckingEventArgs<T>> OnMd5Checking;

        public event EventHandler<OnCompletedEventArgs<T>> OnCompleted;

        #region 属性

        /// <summary>
        /// 工作状态
        /// </summary>
        public WorkState State { get; private set; }

        /// <summary>
        /// 文件源路径
        /// </summary>
        public string SrcPath { get; private set; }

        /// <summary>
        /// 文件的目标路径
        /// </summary>
        public string TgtPath { get; private set; }

        /// <summary>
        /// 临时目录（存放断点数据文件）
        /// </summary>
        public string TempDir { get; private set; }

        /// <summary>
        /// 文件的目标目录
        /// </summary>
        public string TgtDir { get; private set; }

        /// <summary>
        /// 文件大小
        /// </summary>
        public long FileLength { get; private set; }

        /// <summary>
        /// 需传输包的次数
        /// </summary>
        public int PackCount { get; private set; }

        /// <summary>
        /// 总进度：默认 PackCount*2
        /// 分片占100，合并分片占100,校验文件占1
        /// </summary>
        public int MaxProgress { get; private set; }

        /// <summary>
        /// 当前进度
        /// </summary>
        private int CurrProgress;


        /// <summary>
        /// 进度百分比
        /// </summary>
        public decimal CurrRate
        {
            get
            {
                var re = Math.Round((this.CurrProgress * 1.000 / this.MaxProgress * 1.000), 3);
                return (decimal)(re > 1 ? 1 : re);
            }
        }

        public T UserState
        {
            get;
            set;
        }

        public bool Md5Check { get; private set; }

        /// <summary>
        /// 是否忙碌(正在执行)
        /// </summary>
        public bool IsBusy { set; private get; }

        /// <summary>
        /// 执行命令
        /// </summary>
        private ConcurrentQueue<WorkState> QueState { set; get; } = new ConcurrentQueue<WorkState>();

        #endregion


        #region 构造函数

        public MigrateWorker(T userState, string srcPath, string tgtPath, bool md5Check = true)
        {
            this.UserState = userState;
            this.SrcPath = srcPath;
            this.TgtPath = tgtPath;
            this.Md5Check = md5Check;
            this.State = WorkState.等待;

            Init();
        }

        private void Init()
        {
            FileInfo fileInfo = new FileInfo(this.SrcPath);
            if (!fileInfo.Exists)
            {
                throw new ArgumentException("文件不存在！", "srcPath");
            }

            this.TgtDir = Path.GetDirectoryName(this.TgtPath);

            if (!Directory.Exists(this.TgtDir))
            {
                Directory.CreateDirectory(this.TgtDir);
            }

            this.FileLength = fileInfo.Length;

            if ((this.FileLength % MigrateWorker<T>.PackSize) > 0)
            {
                this.PackCount = (int)(this.FileLength / MigrateWorker<T>.PackSize) + 1;
            }
            else
            {
                this.PackCount = (int)(this.FileLength / MigrateWorker<T>.PackSize);
            }

            this.MaxProgress = this.PackCount * 2;

            this.TempDir = Path.Combine(this.TgtDir, StrMD5(Path.GetFileName(this.TgtPath)));
        }

        #endregion

        /// <summary>
        /// 开始执行
        /// </summary>
        public void Start()
        {
            DeleteOver(true);
            this.State = WorkState.执行中;
            this.OnStart?.Invoke(this, new OnStartEventArgs<T>(this.UserState));
            RunWorkerAsync();
        }

        /// <summary>
        /// 取消
        /// </summary>
        public void CancelAsync()
        {
            if (this.IsBusy)
            {
                this.State = WorkState.取消;
            }
            else
            {
                this.CurrProgress = 0;
                DeleteOver(true);
                this.OnUpdateProgress?.Invoke(this.UserState, new OnUpdateProgressEventArgs<T>(this.UserState, this.CurrRate));
            }
        }

        /// <summary>
        /// 暂停
        /// </summary>
        public void PauseAsync()
        {
            this.State = WorkState.暂停;
        }

        /// <summary>
        /// 继续
        /// </summary>
        public void ContinceAsync()
        {
            if (this.IsBusy)
            {
                this.QueState.Enqueue(WorkState.继续);
            }
            else
            {
                this.State = WorkState.继续;
                this.CurrProgress = Directory.GetFiles(this.TempDir).Length;
                this.OnUpdateProgress?.Invoke(this.UserState, new OnUpdateProgressEventArgs<T>(this.UserState, this.CurrRate));
                RunWorkerAsync();
            }
        }

        /// <summary>
        /// 重做
        /// </summary>
        public void RedoAsync()
        {
            if (this.IsBusy)
            {
                this.QueState.Enqueue(WorkState.重做);
            }
            else
            {
                this.State = WorkState.重做;
                this.CurrProgress = 0;
                DeleteOver(true);
                this.OnUpdateProgress?.Invoke(this.UserState, new OnUpdateProgressEventArgs<T>(this.UserState, this.CurrRate));
                RunWorkerAsync();
            }
        }
  


        private object locker = new object();
        private void RunWorkerAsync()
        {
            lock (locker)
            {
                Task.Run(() =>
                {
                    this.State = WorkState.执行中;
                    this.IsBusy = true;
                    try
                    {
                        //临时文件夹路径
                        CheckTempDir();
                        var tempfiles = new DirectoryInfo(this.TempDir).GetFiles();
                        List<string> Comparefiles = new List<string>();
                        for (int j = 0; j < PackCount; j++)
                        {
                            bool hasfile = false;
                            foreach (FileInfo Tempfile in tempfiles)
                            {
                                if (Tempfile.Name == GenerateTempName(j))
                                {
                                    hasfile = true;
                                    break;
                                }
                            }
                            if (hasfile == false)
                            {
                                Comparefiles.Add(j.ToString());
                            }
                        }

                        //最后补上这些缺失的文件                
                        if (this.State != WorkState.暂停 && this.State != WorkState.取消 && Comparefiles.Count > 0)
                        {
                            var tasks = new List<Task>();
                            var fy = Task.Factory;
                            foreach (string com_index in Comparefiles)
                            {
                                string strIndex = com_index;
                                var task = fy.StartNew(() =>
                                {
                                    // 取消 / 暂停 
                                    if (this.State == WorkState.暂停 || this.State == WorkState.取消)
                                    {
                                        return;
                                    }

                                    string tempfilepath = Path.Combine(this.TempDir, GenerateTempName(strIndex));
                                    using (FileStream Compstream = new FileStream(tempfilepath, FileMode.Create, FileAccess.Write, FileShare.Write))
                                    {
                                        int length = (int)Math.Min(PackSize, this.FileLength - Convert.ToInt32(strIndex) * PackSize);
                                        var bytes = GetFile(Convert.ToInt64(strIndex) * PackSize, length);
                                        Compstream.Write(bytes, 0, length);
                                        Compstream.Flush();
                                        Compstream.Close();
                                        Compstream.Dispose();
                                    }

                                    //报告进度
                                    Interlocked.Increment(ref this.CurrProgress);
                                    this.OnUpdateProgress?.Invoke(this, new OnUpdateProgressEventArgs<T>(this.UserState, this.CurrRate));

                                });
                                tasks.Add(task);
                            }
                            //等待所有线程完成
                            Task.WaitAll(tasks.ToArray());
                        }

                        if (this.State != WorkState.暂停 && this.State != WorkState.取消)
                        {
                            var tempDirInfo = new DirectoryInfo(this.TempDir);
                            tempfiles = tempDirInfo.GetFiles();

                            this.CurrProgress = tempfiles.Length;
                            this.OnUpdateProgress?.Invoke(this, new OnUpdateProgressEventArgs<T>(this.UserState, this.CurrRate));

                            using (FileStream writestream = new FileStream(this.TgtPath, FileMode.Create, FileAccess.Write, FileShare.Write))
                            {
                                foreach (FileInfo fileInfo in tempfiles)
                                {
                                    // 取消 / 暂停 
                                    if (this.State == WorkState.暂停 || this.State == WorkState.取消)
                                    {
                                        break;
                                    }

                                    using (FileStream readTempStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                                    {
                                        long onefileLength = fileInfo.Length;
                                        byte[] buffer = new byte[Convert.ToInt32(onefileLength)];
                                        readTempStream.Read(buffer, 0, Convert.ToInt32(onefileLength));
                                        writestream.Write(buffer, 0, Convert.ToInt32(onefileLength));
                                    }

                                    //报告进度
                                    Interlocked.Increment(ref this.CurrProgress);
                                    this.OnUpdateProgress?.Invoke(this, new OnUpdateProgressEventArgs<T>(this.UserState, this.CurrRate));
                                }
                                writestream.Flush();
                                writestream.Close();
                                writestream.Dispose();
                            }
                        }

                        if (this.State != WorkState.暂停 && this.State != WorkState.取消)
                        {
                            bool? isOK = null;
                            if (this.Md5Check)
                            {
                                this.State = WorkState.校验文件;
                                this.OnMd5Checking?.Invoke(this, new OnMd5CheckingEventArgs<T>(this.UserState));
                                isOK = Md5Compare();
                            }

                            DeleteOver(false);
                            this.State = WorkState.完成;
                            this.OnCompleted?.Invoke(this, new OnCompletedEventArgs<T>(this.UserState, isOK));
                        }
                        else
                        {
                            if (this.State == WorkState.取消)
                            {
                                this.CurrProgress = 0;
                                DeleteOver(true);
                                this.OnUpdateProgress?.Invoke(this.UserState, new OnUpdateProgressEventArgs<T>(this.UserState, this.CurrRate));
                            }
                            else if (this.State == WorkState.暂停)
                            {

                            }

                            while (true)
                            {
                                if (this.QueState.Count > 0)
                                {
                                    WorkState wks = WorkState.完成;
                                    #region 尝试获取状态
                                    while (true)
                                    {
                                        if (!this.QueState.TryDequeue(out wks))
                                        {
                                            Thread.Sleep(500);
                                        }
                                        else
                                        {
                                            break;
                                        }
                                    } 
                                    #endregion
                                    this.State = wks;

                                    if (this.State == WorkState.继续)
                                    {
                                        this.State = WorkState.继续;
                                        this.CurrProgress = Directory.GetFiles(this.TempDir).Length;
                                        this.OnUpdateProgress?.Invoke(this.UserState, new OnUpdateProgressEventArgs<T>(this.UserState, this.CurrRate));
                                        RunWorkerAsync();
                                    }
                                    else if (this.State == WorkState.重做)
                                    {
                                        this.State = WorkState.重做;
                                        this.CurrProgress = 0;
                                        DeleteOver(true);
                                        this.OnUpdateProgress?.Invoke(this.UserState, new OnUpdateProgressEventArgs<T>(this.UserState, this.CurrRate));
                                        RunWorkerAsync();
                                    }
                                }
                                else
                                {
                                    break;
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        this.OnError?.Invoke(this, new OnErrorEventArgs<T>(this.UserState, ex));
                    }
                    this.IsBusy = false;
                });
            }
        }

        #region private 方法

        /// <summary>
        /// 根据开始位置获取文件字节流
        /// </summary>
        /// <param name="start"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        private byte[] GetFile(long start, int length)
        {
            using (FileStream ServerStream = new FileStream(this.SrcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 1024 * 80, true))
            {
                byte[] buffer = new byte[length];
                ServerStream.Position = start;
                //ServerStream.Seek(start, SeekOrigin.Begin);
                ServerStream.Read(buffer, 0, length);
                return buffer;
            }
        }

        /// <summary>
        /// 检测临时目录是否存在，不存在则创建
        /// </summary>
        private void CheckTempDir()
        {
            if (!Directory.Exists(this.TempDir))
            {
                Directory.CreateDirectory(this.TempDir);
            }
        }

        /// <summary>
        /// 删除
        /// </summary>
        /// <param name="isDelTgtFile">是否删除 目标文件</param>
        private void DeleteOver(bool isDelTgtFile)
        {
            if (Directory.Exists(this.TempDir))
            {
                Directory.Delete(this.TempDir, true);
            }

            if (isDelTgtFile && File.Exists(this.TgtPath))
            {
                File.Delete(this.TgtPath);
            }
        }


        /// <summary>
        /// 生成临时文件名称
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        private string GenerateTempName(object index)
        {
            string res = index.ToString().PadLeft(this.PackCount.ToString().Length, '0') + "_" + this.PackCount;
            return res;
        }


        /// <summary>
        /// md5比对文件
        /// </summary>
        /// <returns></returns>
        private bool Md5Compare()
        {
            string md51 = FileMD5(this.SrcPath);
            string md52 = FileMD5(this.TgtPath);

            if (md51 == null || md52 == null)
            {
                return false;
            }
            return md51.Equals(md52);
        }


        /// <summary>
        /// 计算文件的Md5
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private static string FileMD5(string path)
        {
            if (!File.Exists(path))
            {
                return null;
            }
            int bufferSize = 1024 * 16;
            byte[] buffer = new byte[bufferSize];
            Stream inputStream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
            HashAlgorithm hashAlgorithm = new MD5CryptoServiceProvider();
            int readLength = 0;//每次读取长度
            var output = new byte[bufferSize];
            while ((readLength = inputStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                //计算MD5
                hashAlgorithm.TransformBlock(buffer, 0, readLength, output, 0);
            }
            //完成最后计算，必须调用(由于上一部循环已经完成所有运算，所以调用此方法时后面的两个参数都为0)
            hashAlgorithm.TransformFinalBlock(buffer, 0, 0);
            string md5 = BitConverter.ToString(hashAlgorithm.Hash).Replace("-", "");
            hashAlgorithm.Clear();
            inputStream.Close();
            inputStream.Dispose();
            return md5;
        }

        /// <summary>
        /// 字符串Md5
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        private static string StrMD5(string source)
        {
            byte[] sor = Encoding.UTF8.GetBytes(source);
            MD5 md5 = MD5.Create();
            byte[] result = md5.ComputeHash(sor);
            StringBuilder strbul = new StringBuilder(40);
            for (int i = 0; i < result.Length; i++)
            {
                strbul.Append(result[i].ToString("x2"));//加密结果"x2"结果为32位,"x3"结果为48位,"x4"结果为64位

            }
            return strbul.ToString();
        }

        #endregion
    }
}
